#ifndef HASHTABLE_H_JM_20121129
#define HASHTABLE_H_JM_20121129

#include "Hash.h"
#include "HashRow.h"
#include "HashRowImpl.h"
#include <memory>
#include <utility>

namespace jmlib {
	/**
	* Sablona pre hashovaciu tabulku. Samotna tabulka ako celok nie je thread-safe 
	* ale riadky v tabulke su thread-safe. Co je postacujuce nakolko velkost 
	* tabulky je pevne dana sablonovym parametrom a jedine co sa dynamicky meni su 
	* obsahy riadkov. Toto riesenie je rychlejsie ako zamykat celu hashovaciu 
	* tabulku pri pristupre k nej. Zamykaju sa len riadky a teda k cakaniu dojde 
	* len v propade ze dojde k pristupu k totoznemu riadku. Praca s odlisnymi 
	* radkami moze prebiehat parallelne.
	* 
	* @tparam KeyT typ kluca v tabulke
	* @tparam ValueT typ hodnot
	* @tpara, SIZE velkost tabulky
	**/
	template<class KeyT, class ValueT, class MutexT, class ScopedLockT>
	class HashTable {
	public:
		/// Typ kluca v tabulke.
		typedef KeyT KeyType;
		/// Typ hodnoty uskaldnenej v tabulke.
		typedef ValueT ValueType;

		// Zistovanie kluca hashovania pro kolizii nie je starostou tabulky ale 
		// toho kto ju vyuziva. Takze bude polozka jednoducha.
		//typedef ValueType HashEntry;
		/// Typ riadku tabulky.
		
		
		typedef HashRow<KeyType, ValueType> HashRowType;
		typedef HashRowImpl<KeyType, ValueType, MutexT, ScopedLockT> HashRowImplType;		
		typedef typename HashRowType::RowEntry HashEntry;
		typedef typename HashRowType::ExtendedRowEntry ExtendedHashEntry;

		typedef typename HashRowType::ForechEntryFunType ForechEntryFunType;
		typedef typename HashRowType::ForechExtendedEntryFunType ForechExtendedEntryFunType;
		/// Typ ukazovatela na funktor poskytujuci samotnu hashovaciu funkciu.
		typedef std::shared_ptr<Hash<KeyType>> HashFunctorPtr;
		/// Velkos hashovacej tabulky.

		HashTable(const size_t N, const HashFunctorPtr& hash) 
			: /*size_(N),*/ hash_(hash),  synchronized_(true) {

				setSizeAsSquare(N);
				hashTable_ = new HashRowImplType[size_];
		}

		virtual ~HashTable() {
			delete[] hashTable_;
		}

		void realloc(size_t newSize) {
			clear();
			delete[] hashTable_;
			setSizeAsSquare(newSize);
			hashTable_ = new HashRowImplType[size_];
		}
		size_t getSize() const { return size_; }
		/**
		* Zahesovanie kluca na poziciu v tabulke.
		* K hashovai sa pozuziva {@link hash_}, ktory sa nastavuje v konstruktore 
		* tabulky. Pokial je takto vypocitany hash vacsi ako je velkost tabulky 
		* {@link SIZE}, tak sa pouzije operacia modulo aby nepresahoval pozadovany 
		* rozmer.
		* 
		* @param key kluc ku ktoremu sa hahom hlada pozicia v tabulke
		* @retun pozicia v tabulke
		**/
		size_t hash(const KeyType& key) const {
			const /*unsigned int*/ size_t index = (*hash_)(key);
			//assert(index < TABLE_SIZE);
			return index % size_;
			//return index & moduler_;
		}

		/**
		* Pridaj hodnotu na poziciu urcenu klucom do tabulky.
		* 
		* @param key kluc pridavanej hodnoty
		* @param value pridavana hodnota
		**/
		int add(const KeyType& key, const ValueType& value) {
			size_t index = hash(key);
			return getRow(index)->add(key, value);
		}

		bool addIf(const KeyType& key, const ValueType& value, 
			const HashRowActionCondition& cond) {
				size_t index = hash(key);
				return getRow(index)->addIf(key, value, cond);
		}


		//double getAggregatedLoadFactor() {
		//	size_t count = 0;
		//	for (size_t i = 0; i < size_; ++i) {
		//		count += hashTable_[i].getAggregatedEntryCount();
		//	}
		//	return count/(double)size_;
		//}


		double getLoadFactor() {
			size_t count = 0;
			for (size_t i = 0; i < size_; ++i) {
				count += hashTable_[i].getExtendedEntryCount();
			}
			return count/(double)size_;
		}

		double getHashLoadFactor() {
			size_t count = 0;
			for (size_t i = 0; i < size_; ++i) {
				if (!hashTable_[i].isEmpty()) {
					count ++;
				}
			}
			return count/(double)size_;
		}

		/**
		* Vykonanie akcie definovanej funktorom na vsetkych hodnotach leziacich 
		* v riadku, ktory je urceny predanym klucom.
		* 
		* @param key kluc identifikujuci riadok
		* @param functor akcia vykonana na hodnotach v riadku
		* 
		* @tparam T typ funktora, ktory urcuje operaciu vykonanu na hodnotach 
		*         v riadku tj. akceptuje prave jednu dvojicu (kluc, hodnota) 
		*         na jedno volanie
		**/
		void applyOn(const KeyType& key, const ForechEntryFunType& functor) {
			size_t index = hash(key);
			getRow(index)->applyOn(functor, key);
		}

		bool applyOnExtended(const KeyType& key, const ForechExtendedEntryFunType& functor) {
			size_t index = hash(key);
			return getRow(index)->applyOnExtended(functor, key);
		}
		/**
		* Vykonanie akcie definovanej funktorom na vsetkych hodnotach v tabulke.
		* 
		* @param functor akcia vykonana na hodnotach v tabulke
		* 
		* @tparam T typ funktora, ktory urcuje operaciu vykonanu na hodnotach
		*         tj. akceptuje prave jednu dvojicu (kluc, hodnota) na jedno 
		*         volanie
		**/
		
		void foreach(const ForechEntryFunType& functor) {
			for (size_t i = 0; i < size_; ++i) {
				getRow(i)->foreach(functor);
			}		
		}

		void foreachExtended(const ForechExtendedEntryFunType& functor) {
			for (size_t i = 0; i < size_; ++i) {
				getRow(i)->foreachExtended(functor);
			}		
		}

		/**
		* Vykonanie akcie definovanej funktorom na vsetkych hodnotach v tabulke 
		* pricom predavane hodnoty su doplnene indexom riadku v tabulke (hashom).
		* 
		* urcene pre debugovanie.
		* 
		* @param functor akcia vykonana na hodnotach v tabulke
		* 
		* @tparam T typ funktora, ktory urcuje operaciu vykonanu na hodnotach 
		*         tj. akceptuje prave dva parametre a to dvojicu (kluc, hodnota) 
		*         a ich hash na jedno volanie
		**/
		template<class T>
		void foreachIndexed(const T& functor) {
			for (size_t i = 0; i < TABLE_SIZE; ++i) {
				getRow(i)->foreach([&functor, i](HashEntry& entry) {
					functor(entry, i);
				});
			}		
		}

		/**
		* Odstranenie zaznamu zahesovaneho pod tymto klucom.
		* odstranic len zaznam, ktory ma presne rovnaky kluc. Zaznamy, ktore sa 
		* hasuju do rovnakeho riadku ale pod inym klucom (tj. kolizia) 
		* su ponechane nadalej v tabulke.
		* 
		* @return true -> zaznam bol zmazany, false -> zaznam z danym klucom 
		* neexistuje a nie je teda co mazat
		**/
		bool remove(const KeyType& key) {
			size_t index = hash(key);
			return getRow(index)->remove(key);
		}

		/**
		* Je pod zadanym klucom nieco v tabulke ulozene?
		**/
		bool hasKey(const KeyType& key) {
			size_t index = hash(key);
			return getRow(index)->hasKey(key);
		}

		/**
		* Zamze vsetky zahashovane data v tabulke.
		**/
		void clear() {
			for (size_t i = 0; i < size_; ++i) {
				getRow(i)->clear();
			}
		}

		HashRowType* getRow(const size_t& index) {
			if (synchronized_) {
				return hashTable_[index].getLockableInstance();
			}
			else {
				return hashTable_[index].getLockFreeInstance();
			} 
		}

		bool isSynchronized() const { return synchronized_; }
		void setSynchronized(bool val) { synchronized_ = val; }
	private:
		size_t size_;
		unsigned int moduler_;
		/// Pevne dana hashovacia tabulka.
		HashRowImplType* hashTable_;
		/// Funktor poskytujuci hashovaciu funkciu.
		HashFunctorPtr hash_;
		bool synchronized_;

		void setSizeAsSquare(const size_t n) {
			//size_ = 1;
			//while (size_ < n) {
			//	size_ <<= 1;
			//}
			//moduler_ = size_-1;
			size_ = n;
		}

	};

	
} // namespace jmlib

#endif
